// Copyright 2011-2012, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_BOOST_PARSE_H_
#define LIBV_BOOST_PARSE_H_

#include <cctype>
#include <exception>
#include <string>
#include <boost/cstdint.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/mpl/assert.hpp>
#include <boost/numeric/conversion/cast.hpp>
#include <boost/static_assert.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/type_traits/is_unsigned.hpp>
#include <boost/rational.hpp>
#include <boost/utility/enable_if.hpp>
#include <libv/boost/parse.h>
#include <libv/check.h>
#include <libv/define.h>
#include <libv/typed_integer.h>



namespace libv {

LIBV_DEFINE_SIMPLE_EXCEPTION(ParseException)
LIBV_DEFINE_DERIVED_EXCEPTION(ParseIntException, ParseException)

template<class T> T Parse(const ::std::string&);

namespace internal {

// Расширение boost::lexical_cast для целочисленных типов, исправляющее
// его "недочеты", основанные на точности соответствия стандарту:
//  - Шаблонные аргументы int8_t и uint8_t не рассматриваются как char.
//  - Отрицательное значение не переводится в беззнаковый тип через двоичное
//    дополнение.
// Использование данной функции вместо единоразового преобразования через
// stringstream быстрее на порядок, так как не создается сложных объектов.
// @exception ParseIntException.
struct ParseImpl {
  public:
    template<class T>
    static T DoParse(const ::std::string& str) {
        if ( str.empty() ) {
            throw ParseException("Can't parse empty string");
        }

        try {
            // Код довольно запутанный,
            // зато все максимально перенесено на этап компиляции.
            return DoParse<T>(str, static_cast<T*>(NULL));
        } catch(boost::bad_lexical_cast& e) {
            throw ParseException(e.what());
        } catch(boost::numeric::bad_numeric_cast& e) {
            throw ParseIntException(e.what());
        }
    }


  private:
    template<class Ti8>
    static typename boost::enable_if<boost::is_same<Ti8, int8_t>, Ti8>::type
    DoParse(const ::std::string& str, Ti8*) {
        return DoLexicalCastThroughInt<Ti8>(str);
    }

    template<class Tui8>
    static typename boost::enable_if<boost::is_same<Tui8, uint8_t>, Tui8>::type
    DoParse(const ::std::string& str, Tui8* dummy) {
        VerifyUnsigned(str, dummy);
        return DoLexicalCastThroughInt<Tui8>(str);
    }

    template<class TBool>
    static typename boost::enable_if<boost::is_same<TBool, bool>, TBool>::type
    DoParse(const ::std::string& str, TBool*) {
        // В c# bool(true).ToString() == "True", а в C++ принято все строчными.
        if ( str.size() > 1 ) {
            if ( boost::iequals(str, "true") ) {
                return true;
            } else if ( boost::iequals(str, "false") ) {
                return false;
            } else {
                throw ParseException("Bad bool string");
            }
        }
        return boost::lexical_cast<TBool>(str);
    }

    template<class T, class TInt>
    static T DoParse(const ::std::string& str, libv::TypedInteger<T, TInt>*) {
        return T(libv::Parse<TInt>(str));
    }

    // Будет выбрана только если не подойдут предыдущие 2 вызова,
    // так как требует преобразования типа.
    template<class T>
    static T DoParse(const ::std::string& str, void*) {
        VerifyUnsigned(str, static_cast<T*>(NULL));
        return boost::lexical_cast<T>(str);
    }



    template<class T>
    static T DoLexicalCastThroughInt(const ::std::string& str) {
        BOOST_STATIC_ASSERT(sizeof(int) > sizeof(T));
        return boost::numeric_cast<T>(boost::lexical_cast<int>(str));
    }



    template<class T>
    static void VerifyUnsigned(
        const ::std::string& str,
        typename boost::enable_if<boost::is_unsigned<T>, T>::type*) {
        // Основываясь на стандарте lexical_cast не считает ошибкой
        // преобразование отрицательного числа к беззнаковому типу
        // через двоичное дополнение.
        if ( !str.empty() && str[0] == '-' ) {
            throw ParseIntException("Can't cast negative value to unsigned");
        }
    }

    static void VerifyUnsigned(const ::std::string&, void*) {}
};

}  // internal



template<class T>
T Parse(const ::std::string& str) {
    return internal::ParseImpl::DoParse<T>(str);
}




namespace internal {

template<class TInt, class T>
TInt PowOf10(const T pow) {
    assert(pow >= 0);

    // Степени будут маленькие, и алгоритм за O(log), но с if-ами будет хуже.
    TInt result = 1;
    for ( T i = 0 ; i != pow ; ++i ) {
        result *= 10;
    }
    return result;
}

}  // internal

// Поддерживаются: 1, 1.0, 1., .1, 1E5, 1.0E5, 1.5e-3, ...
inline boost::rational<int64_t> ParseRational(const ::std::string& value) {
    LIBV_PRECOND(!value.empty());

    const size_t point_index = value.find_first_of('.');
    const size_t e_index = value.find_first_of("eE");

    // Это целое число, возможно, со знаком.
    if ( point_index == ::std::string::npos && e_index == ::std::string::npos ){
        return boost::rational<int64_t>(libv::Parse<int64_t>(value));
    }

    // Или точки нет или она всегда левее "E", который, возможно, отсутствует.
    // Эта проверка обзятельно нужна алгоритму ниже.
    LIBV_VERIFY(point_index == ::std::string::npos || point_index < e_index);



    // Целая часть.
    const char v = value[0];
    const size_t with_sign = static_cast<size_t>(v == '+' || v == '-');
    const size_t num_len = ::std::min(point_index, e_index);
    int64_t num = 0;
    if ( num_len != with_sign ) {
        num = libv::Parse<int64_t>(value.substr(0, num_len));
    }

    // Дробная часть.
    int64_t den = 0;
    size_t den_len = 0;
    if ( point_index < value.size() - 1 ) {  // не npos и не "1."
        LIBV_VERIFY(::std::isdigit(value[point_index + 1]) != 0);
        const size_t den_end = ::std::min(e_index, value.size());

        // int64_t позволяет записать любое число, не более чем из 18 знаков
        // отбросим лишнее (что вряд ли), double — 16 знаков после запятой.
        den_len = ::std::min(den_end - 1U - point_index, size_t(18));
        den = libv::Parse<int64_t>(value.substr(point_index + 1, den_len));
    }

    // Или ".0" или "0.", но не ".".
    LIBV_VERIFY(num_len > 0 || den_len > 0);


    // Степень, возможно, со знаком.
    int power = 0;
    if ( e_index != ::std::string::npos ) {
        const size_t power_len = value.size() - 1 - e_index;

        // в double самая длинная степерь это "-263".
        LIBV_VERIFY(0 <= power_len && power_len < 5U);
        power = libv::Parse<int>(value.substr(e_index + 1, power_len));
    }


    // 1.25E3 -> ( 1*100 + 25 ) * 10 = 1250.
    int64_t num_pow = 1;
    if ( num != 0 ) {
        num_pow = internal::PowOf10<int64_t>(den_len);
    }

    const int sign = value[0] == '-' ? -1 : 1;
    const int full_den_power = static_cast<int>(den_len) - power;

    if ( full_den_power >= 0 ) {
        const int64_t den_pow = internal::PowOf10<int64_t>(full_den_power);
        return boost::rational<int64_t>(num * num_pow + sign * den, den_pow);
    } 

    const int64_t den_pow = internal::PowOf10<int64_t>(-full_den_power);
    return boost::rational<int64_t>((num * num_pow + sign * den) * den_pow);
}

}  // libv

#endif  // LIBV_BOOST_PARSE_H_
